﻿'Copyright (c) 2008, William Severance, Jr., WESNet Designs
'All rights reserved.

'Redistribution and use in source and binary forms, with or without modification, are permitted
'provided that the following conditions are met:

'Redistributions of source code must retain the above copyright notice, this list of conditions
'and the following disclaimer.

'Redistributions in binary form must reproduce the above copyright notice, this list of conditions
'and the following disclaimer in the documentation and/or other materials provided with the distribution.

'Neither the name of William Severance, Jr. or WESNet Designs may be used to endorse or promote
'products derived from this software without specific prior written permission.

'Disclaimer: THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS
'            OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
'            AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER BE LIABLE
'            FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
'            LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
'            INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
'            OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
'            IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

'Although I will try to answer questions regarding the installation and use of this software when
'such questions are submitted via e-mail to the below address, no promise of further
'support or enhancement is made nor should be assumed.

'Developer Contact Information:
'     William Severance, Jr.
'     WESNet Designs
'     679 Upper Ridge Road
'     Bridgton, ME 04009
'     Phone: 207-647-9375
'     E-Mail: bill@wesnetdesigns.com
'     Website: www.wesnetdesigns.com

Imports System.IO
Imports System.Drawing
Imports System.Drawing.Drawing2D
Imports System.Drawing.Imaging
Imports System.Text.RegularExpressions
Imports System.Web

Namespace WESNet.Utilities
    Public Class ImageUtilities

#Region "Private Constants"
        Private Class Consts
            ' grayscale values
            Public Const GrayRed As Single = 0.3086F     'NTSC weighting = 0.2990F
            Public Const GrayGreen As Single = 0.6094F   'NTSC weighting = 0.5870F
            Public Const GrayBlue As Single = 0.082F     'NTSC weighting = 0.1140F
            ' sepia values
            Public Const SepiaRed As Single = 0.2F
            Public Const SepiaGreen As Single = 0.14F
            Public Const SepiaBlue As Single = 0.08F
            ' resize quality
            Public Const InterpolationQuality As InterpolationMode = InterpolationMode.HighQualityBicubic
        End Class
#End Region

#Region "Enums and Arrays"
        Public Shared AcceptableImageFormats As ImageFormat() = {ImageFormat.Bmp, ImageFormat.Gif, ImageFormat.Jpeg, ImageFormat.Png}

        Public Enum Orientations As Integer
            Landscape = 1
            Portrate = 2
        End Enum

        Public Enum DuplicateActions As Integer
            Replace = 0
            MakeUnique = 1
            Abort = 2
            ThrowException = 3
        End Enum
#End Region

#Region "Orientation, Constraint, and Scaling Methods"

        'Return the orientation (Landscape or Portrate) for a given image size
        Public Overloads Shared Function GetOrientation(ByVal sz As Size) As Orientations
            Return GetOrientation(sz.Width, sz.Height)
        End Function

        'Return the orientation (Landscape or Portrate) for a given image width and height
        Public Overloads Shared Function GetOrientation(ByVal Width As Integer, ByVal Height As Integer) As Orientations
            If Width > Height Then
                Return Orientations.Landscape
            Else
                Return Orientations.Portrate
            End If
        End Function

        'Return the size of an image from its initial size after applying width, height, aspect ratio lock, and maximum magnification factors
        Public Overloads Shared Function Constrain(ByVal InitialSize As Size, ByVal ConstrainingWidth As Integer, _
                                                   ByVal ConstrainingHeight As Integer, ByVal LockAspect As Boolean, _
                                                   ByVal MaxMagnification As Single) As Size
            If LockAspect And (MaxMagnification = 1.0!) And (InitialSize.Width <= ConstrainingWidth) _
                And (InitialSize.Height <= ConstrainingHeight) Then
                Return InitialSize
            Else
                Dim Scale As SizeF = GetScale(InitialSize, ConstrainingWidth, ConstrainingHeight, LockAspect, MaxMagnification)
                Return New Size(CInt(InitialSize.Width * Scale.Width), CInt(InitialSize.Height * Scale.Height))
            End If
        End Function

        Public Overloads Shared Function Constrain(ByVal InitialSize As Size, ByVal ConstrainingSize As Size, _
                                                   ByVal LockAspect As Boolean, ByVal MaxMagnification As Single) As Size
            Return Constrain(InitialSize, ConstrainingSize.Width, ConstrainingSize.Height, LockAspect, MaxMagnification)
        End Function

        Public Overloads Shared Function Constrain(ByVal InitialSize As Size, ByVal ConstrainingWidth As Integer, _
                                                    ByVal ConstrainingHeight As Integer) As Size
            Return Constrain(InitialSize, ConstrainingWidth, ConstrainingHeight, True, 1.0!)
        End Function

        Public Overloads Shared Function Constrain(ByVal InitialSize As Size, ByVal ConstrainingSize As Size) As Size
            Return Constrain(InitialSize, ConstrainingSize.Width, ConstrainingSize.Height, True, 1.0!)
        End Function

        Public Overloads Shared Function GetScale(ByVal InitialSize As Size, ByVal ConstrainingSize As Size, _
                                                  ByVal LockAspect As Boolean, ByVal MaxMagnification As Single) As SizeF
            Return GetScale(InitialSize, ConstrainingSize.Width, ConstrainingSize.Height, LockAspect, MaxMagnification)
        End Function

        Public Overloads Shared Function GetScale(ByVal InitialSize As Size, ByVal ConstrainingSize As Size) As SizeF
            Return GetScale(InitialSize, ConstrainingSize, True, 1.0!)
        End Function

        Public Overloads Shared Function GetScale(ByVal InitialSize As Size, ByVal ConstrainingWidth As Integer, _
                                                  ByVal ConstrainingHeight As Integer) As SizeF
            Return GetScale(InitialSize, ConstrainingWidth, ConstrainingHeight, True, 1.0!)
        End Function

        'Return the scaling factor of an image having an InitialSize after applying constraining width, height, aspect ratio
        'lock and maximum magnification factors.
        Public Overloads Shared Function GetScale(ByVal InitialSize As Size, _
                                                  ByVal ConstrainingWidth As Integer, ByVal ConstrainingHeight As Integer, _
                                                  ByVal LockAspect As Boolean, ByVal MaxMagnification As Single) As SizeF

            Dim w As Integer = InitialSize.Width
            Dim h As Integer = InitialSize.Height
            Dim wscale As Double = Math.Min(ConstrainingWidth / w, MaxMagnification)
            Dim hscale As Double = Math.Min(ConstrainingHeight / h, MaxMagnification)
            If LockAspect Then
                Select Case GetOrientation(w, h)
                    Case Orientations.Landscape
                        hscale = wscale
                    Case Orientations.Portrate
                        wscale = hscale
                End Select
            End If
            Return New SizeF(Convert.ToSingle(wscale), Convert.ToSingle(hscale))
        End Function
#End Region

#Region "Matrix methods (brightness, contrast, saturation, grayscale, sepia, custom) "

        'Adjust the image brightness up or down by a percentage
        Public Shared Sub AdjustBrightness(ByVal img As Bitmap, ByVal percent As Integer)
            TransformImage(img, GetBrightnessMatrix(percent))
        End Sub

        'Adjust the image contrast up or down by a percentage
        Public Shared Sub AdjustContrast(ByVal img As Bitmap, ByVal percent As Integer)
            TransformImage(img, GetContrastMatrix(percent))
        End Sub

        'Adjust the image saturation up or down by a percentage
        Public Shared Sub AdjustSaturation(ByVal img As Bitmap, ByVal percent As Integer)
            TransformImage(img, GetSaturationMatrix(percent))
        End Sub

        'Convert the image to grayscale
        Public Shared Sub ConvertToGrayscale(ByVal img As Bitmap)
            TransformImage(img, GetGrayscaleMatrix())
        End Sub

        'Convert the image to sepia
        Public Shared Sub ConvertToSepia(ByVal img As Bitmap)
            TransformImage(img, GetSepiaMatrix())
        End Sub

        'Adjust the image using a custom matrix
        Public Shared Sub AdjustUsingCustomMatrix(ByVal img As Bitmap, ByVal matrix As Single()())
            TransformImage(img, matrix)
        End Sub

        'Return the matrix to adjust for a specified brightness percentage change
        Public Shared Function GetBrightnessMatrix(ByVal percent As Integer) As Single()()
            ' set brightness by setting the offset row in the matrix
            ' values can range from -1.0 (black) to 1.0 (white)
            ' map the percent to use 60% of the full range (-0.6 to 0.6)
            Dim v As Single = 0.006F * percent

            ' setup the matrix
            Dim matrix As Single()() = { _
              New Single() {1, 0, 0, 0, 0}, _
              New Single() {0, 1, 0, 0, 0}, _
              New Single() {0, 0, 1, 0, 0}, _
              New Single() {0, 0, 0, 1, 0}, _
              New Single() {v, v, v, 0, 1}}

            Return matrix
        End Function

        'Return the matrix to adjust for a specified specified contrast percentage change
        Public Shared Function GetContrastMatrix(ByVal percent As Integer) As Single()()
            ' perform contrast by setting the scale and offset at the same time
            ' calculate the scale and offset
            Dim v As Single
            If percent > 0 Then
                ' range from 0.0 (no change) to 3.8 (high contrast)
                v = 0.0195F * percent
                v *= v
            Else
                ' negative value, -1.0 is gray and 0.0 is no change
                v = 0.009F * percent
            End If

            Dim scale As Single = 1 + v
            Dim offset As Single = v / 2

            ' setup the matrix
            Dim matrix As Single()() = { _
             New Single() {scale, 0, 0, 0, 0}, _
             New Single() {0, scale, 0, 0, 0}, _
             New Single() {0, 0, scale, 0, 0}, _
             New Single() {0, 0, 0, 1, 0}, _
             New Single() {-offset, -offset, -offset, 0, 1}}

            Return matrix
        End Function

        'Return the matrix to adjust for the specified saturation percentage change
        Public Shared Function GetSaturationMatrix(ByVal percent As Integer) As Single()()
            ' set the saturation by scaling the red, green and blue values
            ' a value of 0 is grayscale, 1.0 is the orginial (no change) and 
            ' above 2.0 starts to look unnatural

            ' calculate the scaling offset to apply to the red, green and blue colors
            Dim v As Single
            If percent > 0 Then
                ' range from 1 (no change) to 2.5 (a lot of saturation)
                v = 1.0F + (0.015F * percent)
            Else
                ' range from 0.25 (almost grayscale) to 1 (no change)
                v = 1.0F - (0.0075F * -percent)
            End If

            Dim red As Single = (1.0F - v) * Consts.GrayRed
            Dim green As Single = (1.0F - v) * Consts.GrayGreen
            Dim blue As Single = (1.0F - v) * Consts.GrayBlue

            ' setup the matrix
            Dim matrix As Single()() = { _
             New Single() {red + v, red, red, 0, 0}, _
             New Single() {green, green + v, green, 0, 0}, _
             New Single() {blue, blue, blue + v, 0, 0}, _
             New Single() {0, 0, 0, 1, 0}, _
             New Single() {0, 0, 0, 0, 1}}

            Return matrix
        End Function

        'Return the matrix to convert to grayscale
        Public Shared Function GetGrayscaleMatrix() As Single()()
            ' set the luminosity of the colors using grayscale weighting
            Dim matrix As Single()() = { _
             New Single() {Consts.GrayRed, Consts.GrayRed, Consts.GrayRed, 0, 0}, _
             New Single() {Consts.GrayGreen, Consts.GrayGreen, Consts.GrayGreen, 0, 0}, _
             New Single() {Consts.GrayBlue, Consts.GrayBlue, Consts.GrayBlue, 0, 0}, _
             New Single() {0, 0, 0, 1, 0}, _
             New Single() {0, 0, 0, 0, 1}}

            Return matrix
        End Function

        'Return the matrix to convert to sepia
        Public Shared Function GetSepiaMatrix() As Single()()
            ' sepia looks better if first darken the image, the matrix values 
            ' (combination of brightness and sepia) could be hard coded, 
            ' but leave in code to make it easy to modify
            Dim bright As Single()() = GetBrightnessMatrix(-25)

            ' setup the matrix, sepia is grayscale with a slight 
            ' offset in the red, green and blue colors
            Dim sepia As Single()() = { _
             New Single() {Consts.GrayRed, Consts.GrayRed, Consts.GrayRed, 0, 0}, _
             New Single() {Consts.GrayGreen, Consts.GrayGreen, Consts.GrayGreen, 0, 0}, _
             New Single() {Consts.GrayBlue, Consts.GrayBlue, Consts.GrayBlue, 0, 0}, _
             New Single() {0, 0, 0, 1, 0}, _
             New Single() {Consts.SepiaRed, Consts.SepiaGreen, Consts.SepiaBlue, 0, 1}}

            Return CombineMatrix(bright, sepia)
        End Function

        'Return a matrix that is the combination of the two specified matrixes,
        'while maintaining the order information of the matrix
        Public Shared Function CombineMatrix(ByVal m1 As Single()(), ByVal m2 As Single()()) As Single()()
            Dim matrix As Single()() = { _
             New Single() {0, 0, 0, 0, 0}, _
             New Single() {0, 0, 0, 0, 0}, _
             New Single() {0, 0, 0, 0, 0}, _
             New Single() {0, 0, 0, 0, 0}, _
             New Single() {0, 0, 0, 0, 0}}

            For x As Integer = 0 To 4
                For y As Integer = 0 To 4
                    matrix(y)(x) = _
                     m1(y)(0) * m2(0)(x) + _
                     m1(y)(1) * m2(1)(x) + _
                     m1(y)(2) * m2(2)(x) + _
                     m1(y)(3) * m2(3)(x) + _
                     m1(y)(4) * m2(4)(x)
                Next
            Next

            Return matrix
        End Function

#End Region

#Region "Gamma adjustment methods"

        'Adjust the image gamma by a percentage
        Public Shared Sub AdjustGamma(ByVal img As Bitmap, ByVal percent As Integer)
            ' setup attributes with gamma value
            Dim attr As New ImageAttributes
            Try
                ' update the image
                attr.SetGamma(GetGamma(percent))
                TransformImage(img, attr)
            Finally
                attr.Dispose()
            End Try
        End Sub

        'Return the gamma value specified by a percentage
        Public Shared Function GetGamma(ByVal percent As Integer) As Single
            ' the gamma value can range from 0.1 (bright) to 5.0 (dark)
            ' we allow the range of 0.15 to 3.5
            Dim v As Single
            If percent > 0 Then
                ' range from 1.0 (no change) to 0.15 (brighter)
                v = 1.0F - (0.0085F * percent)
            Else
                ' range from 3.5 (darker) to 1.0 (no change)
                v = 1.0F + (0.025F * -percent)
            End If

            Return v
        End Function

#End Region

#Region "Image rotate, flip, crop, resize, and make thumbnail methods"

        ' rotate the image using built in RotateFlip method
        Public Shared Sub RotateFlip(ByVal img As Bitmap, ByVal flipType As RotateFlipType)
            ' use the built in rotate method
            img.RotateFlip(flipType)
        End Sub

        ' return a new bitmap containing the specified crop rectangle area of the specified image
        Public Shared Function Crop(ByVal src As Bitmap, ByVal cropRect As Rectangle) As Bitmap
            ' create new image that will contain the cropped area
            Dim dest As New Bitmap(cropRect.Width, cropRect.Height)
            dest.SetResolution(src.HorizontalResolution, src.VerticalResolution)
            Dim g As Graphics
            Try
                g = Graphics.FromImage(dest)
                Dim destRect As New Rectangle(0, 0, cropRect.Width, cropRect.Height)
                g.InterpolationMode = Consts.InterpolationQuality
                g.DrawImage(src, destRect, cropRect, GraphicsUnit.Pixel)
            Finally
                If Not g Is Nothing Then g.Dispose()
            End Try
            Return dest
        End Function

#End Region

#Region "Image transform, drawing and streaming methods"

        'Transform an image using the supplied matrix
        Private Overloads Shared Sub TransformImage(ByVal img As Bitmap, ByVal matrix As Single()())
            ' setup the attributes class
            Dim cm As New ColorMatrix(matrix)
            Dim attr As New ImageAttributes
            Try
                ' draw the image
                attr.SetColorMatrix(cm)
                TransformImage(img, attr)
            Finally
                attr.Dispose()
            End Try
        End Sub

        'Transform an image using the supplied attributes
        Private Overloads Shared Sub TransformImage(ByVal img As Bitmap, ByVal attr As ImageAttributes)
            Dim g As Graphics = Graphics.FromImage(img)
            Try
                'Draw the image using the attributes class
                Dim destRect As New Rectangle(0, 0, img.Width, img.Height)
                g.InterpolationMode = Consts.InterpolationQuality
                g.DrawImage(img, destRect, 0, 0, img.Width, img.Height, GraphicsUnit.Pixel, attr)
            Finally
                g.Dispose()
            End Try
        End Sub

        'Return an ImageCodecInfo object for the image/jpeg mime type
        Public Shared Function JPEGCodec() As ImageCodecInfo
            'Setup a JPEG codec
            JPEGCodec = Nothing
            For Each Codec As ImageCodecInfo In ImageCodecInfo.GetImageEncoders
                If Codec.MimeType = "image/jpeg" Then
                    JPEGCodec = Codec
                    Exit For
                End If
            Next 'Supposedly this cannot fail as image/jpg is built-in to GDI+ encoders
        End Function

        'Return the EncoderParameters object for JPEG encoding having a specified Quality
        Public Shared Function JPEGQualityEncoder(ByVal Quality As Long) As EncoderParameters
            Dim EncParams As EncoderParameters = New EncoderParameters(1)
            EncParams.Param(0) = New EncoderParameter(Encoder.Quality, Quality)
            Return EncParams
        End Function

        'Saves a JPEG image src to the supplied stream with specified encoder quality
        Public Overloads Shared Sub SaveJPEG(ByVal src As Bitmap, ByVal quality As Long, ByVal stream As Stream)
            src.Save(stream, JPEGCodec, JPEGQualityEncoder(quality))
        End Sub

        'Saves a JPEG image to the filePath
        Public Overloads Shared Sub SaveJPEG(ByVal src As Bitmap, ByVal Quality As Long, ByVal filePath As String)
            Dim fs As FileStream
            Try
                If File.Exists(filePath) = True Then
                    File.Delete(filePath)
                End If
                fs = New FileStream(filePath, FileMode.Create)
                SaveJPEG(src, Quality, fs)
            Finally
                If Not fs Is Nothing Then fs.Dispose()
            End Try
        End Sub

        'Saves a GIF image to the supplied stream optionally using the OctreeQuantizer
        'written by Brendon Tompkins and available without restrictions on its use at
        'http://codebetter.com/blogs/brendan.tompkins/archive/2007/06/14/gif-image-color-quantizer-now-with-safe-goodness.aspx

        'The OctreeQuantizer provides a far superior quantization to the
        'reduced web color palatte of a gif image than that of the default
        'GDI + code. However, as it uses unmanaged code it REQUIRES FULL TRUST
        'which may not be available in the shared hosting situation. In this case
        'a SecurityException will be thrown and the default web-safe palatte
        'will be used.

        Public Overloads Shared Sub SaveGIF(ByVal src As Bitmap, ByVal UseOctreeQuantizer As Boolean, ByVal stream As Stream)
            Try
                If UseOctreeQuantizer And HasSecurityPermission() Then
                    Dim quantitizer As New ImageQuantization.OctreeQuantizer(255, 8)
                    Dim dest As Bitmap = quantitizer.Quantize(src)
                    dest.Save(stream, ImageFormat.Gif)
                Else
                    src.Save(stream, ImageFormat.Gif)
                End If
            Catch ex As System.Security.SecurityException
                src.Save(stream, ImageFormat.Gif)
            End Try
        End Sub

        'Test if sufficient permissions (Full Trust) to use Octree Quantizer which runs
        'unmanaged code
        Private Shared Function HasSecurityPermission() As Boolean
            Return (System.Security.SecurityManager.IsGranted(New System.Security.Permissions.SecurityPermission(System.Security.Permissions.PermissionState.Unrestricted)))
        End Function

        'Saves a GIF image to the filePath
        Public Overloads Shared Sub SaveGIF(ByVal src As Bitmap, ByVal UseOctreeQuantizer As Boolean, ByVal filePath As String)
            Dim fs As FileStream
            Try
                If File.Exists(filePath) = True Then
                    File.Delete(filePath)
                End If
                fs = New FileStream(filePath, FileMode.Create)
                SaveGIF(src, UseOctreeQuantizer, fs)
            Finally
                If Not fs Is Nothing Then fs.Dispose()
            End Try
        End Sub

        'Saves a PNG image to the supplied stream
        Public Overloads Shared Sub SavePNG(ByVal src As Bitmap, ByVal stream As Stream)
            Dim ms As MemoryStream
            Try
                ms = New MemoryStream
                src.Save(ms, ImageFormat.Png)
                ms.WriteTo(stream)
            Finally
                If Not ms Is Nothing Then ms.Dispose()
            End Try
        End Sub

        'Saves a PNG image to the filePath
        Public Overloads Shared Sub SavePNG(ByVal src As Bitmap, ByVal filePath As String)
            Dim fs As FileStream
            Try
                If File.Exists(filePath) = True Then
                    File.Delete(filePath)
                End If
                fs = New FileStream(filePath, FileMode.Create)
                SavePNG(src, fs)
            Finally
                If Not fs Is Nothing Then fs.Dispose()
            End Try
        End Sub

        'Creates a resized image from the supplied src image contrained by the maximum size, aspect ratio lock and maximum magnification
        'A blank image having MaxSize and Color.Black is returned if src is null
        Public Overloads Shared Function CreateResizedImage(ByVal src As Bitmap, ByVal MaxSize As Size, _
                                                             ByVal LockAspect As Boolean, ByVal MaxMagnification As Single) As Bitmap
            If src Is Nothing Then
                Return CreateBlankImage(MaxSize, Color.Black)
            Else
                Dim destSize As Size = Constrain(src.Size, MaxSize, LockAspect, MaxMagnification)
                If (src.PixelFormat And PixelFormat.Indexed) <> 0 Then
                    Return New Bitmap(src, destSize)
                Else
                    Dim g As Graphics
                    Dim dest As Bitmap
                    Try
                        dest = New Bitmap(destSize.Width, destSize.Height, src.PixelFormat)
                        dest.SetResolution(src.HorizontalResolution, src.VerticalResolution)
                        g = Graphics.FromImage(dest)
                        Dim destRect As New Rectangle(0, 0, destSize.Width, destSize.Height)
                        g.InterpolationMode = Consts.InterpolationQuality
                        g.DrawImage(src, destRect, 0, 0, src.Width, src.Height, GraphicsUnit.Pixel)
                    Finally
                        If Not g Is Nothing Then g.Dispose()
                    End Try
                    Return dest
                End If
            End If
        End Function

        Public Overloads Shared Function CreateResizedImage(ByVal src As Bitmap, ByVal MaxWidth As Integer, _
                                                            ByVal MaxHeight As Integer, ByVal LockAspect As Boolean, _
                                                            ByVal MaxMagnification As Single) As Bitmap
            Return CreateResizedImage(src, New Size(MaxWidth, MaxHeight), LockAspect, MaxMagnification)
        End Function

        'Creates a blank image (Format24bppRgb pixel format) having the size and color specified
        Public Overloads Shared Function CreateBlankImage(ByVal sz As Size, ByVal Color As Color) As Bitmap
            Dim img As Bitmap

            Dim g As Graphics
            Try
                img = New Bitmap(sz.Width, sz.Height, Imaging.PixelFormat.Format24bppRgb)
                g = Graphics.FromImage(img)
                g.Clear(Color)
            Finally
                If Not g Is Nothing Then g.Dispose()
            End Try
            Return img
        End Function

        Public Overloads Shared Function CreateBlankImage(ByVal width As Integer, ByVal height As Integer, ByVal Color As Color) As Bitmap
            Return CreateBlankImage(New Size(width, height), Color)
        End Function

        'Returns a deep copy of a bitmap loaded from a stream
        Public Shared Function LoadImage(ByVal stream As Stream) As System.Drawing.Bitmap
            Dim orig As System.Drawing.Bitmap = Nothing
            Dim copy As System.Drawing.Bitmap = Nothing
            Dim g As System.Drawing.Graphics = Nothing

            Try
                orig = New System.Drawing.Bitmap(stream)

                'Note: When a bitmap image is created from a file or stream, the file or stream remains
                'locked until the image object is disposed preventing saving of the modified image back to the
                'same file. If the file is locked, later use of Bitmap.Save() will throw the error "A generic occurred in GDI+."
                'By drawing the image opened from the stream to a new image of the same size and format, we
                'can then dispose of the original image bitmap and release the file lock.
                'Since we cannot create a Graphics object from an image having an Indexed pixel format (gif)
                'we use Format24bppRgb instead.

                If Not orig Is Nothing Then
                    Dim fmt As PixelFormat = orig.PixelFormat
                    If (fmt And PixelFormat.Indexed) <> 0 Then
                        fmt = PixelFormat.Format24bppRgb
                    End If
                    copy = New System.Drawing.Bitmap(orig.Width, orig.Height, fmt)
                    copy.SetResolution(orig.HorizontalResolution, orig.VerticalResolution)
                    g = System.Drawing.Graphics.FromImage(copy)
                    g.InterpolationMode = Consts.InterpolationQuality
                    g.DrawImage(orig, 0, 0)
                End If

            Finally
                If Not g Is Nothing Then g.Dispose()
                If Not orig Is Nothing Then orig.Dispose()
            End Try
            'If copy Is Nothing Then copy = CreateBlankImage(MaxImageSize, System.Drawing.Color.Black)
            Return copy
        End Function

        'Streams an image of a particular ContentType onto the HttpResponse.OutputStream. For JPG images the encoder Quality may be specified. For GIF images
        'the Octree Quantizer may be used for palete optimization
        Public Shared Sub StreamImage(ByVal img As Bitmap, ByVal ContentType As String, ByVal Quality As Long, ByVal UseOctreeQuantizer As Boolean, ByVal Response As HttpResponse)
            Response.Clear()
            If String.IsNullOrEmpty(ContentType) Then
                Response.ContentType = "image/jpeg"
            Else
                Response.ContentType = ContentType
            End If
            Select Case Response.ContentType
                Case "image/jpeg"
                    SaveJPEG(img, Quality, Response.OutputStream)
                Case "image/gif"
                    SaveGIF(img, UseOctreeQuantizer, Response.OutputStream)
                Case "image/png"
                    SavePNG(img, Response.OutputStream)
                Case Else
                    img.Save(Response.OutputStream, GetImageFormat(Response.ContentType))
            End Select
            Response.End()
            img.Dispose()
        End Sub

#End Region

#Region "Content/mime type methods"

        'Returns true if the src bitmap is one of the acceptable image formats
        Public Shared Function IsAcceptableFormat(ByVal src As Bitmap) As Boolean
            Return Array.IndexOf(AcceptableImageFormats, src.RawFormat) <> -1
        End Function

        'Returns the content type based on a filename extension
        Public Shared Function GetContentType(ByVal ext As String) As String
            Select Case ext.TrimStart("."c).ToLower
                Case "jpg", "jpeg", "jpe" : Return "image/jpeg"
                Case "gif" : Return "image/gif"
                Case "bmp" : Return "image/bmp"
                Case "png" : Return "image/png"
                Case "ico" : Return "image/x-icon"
                Case "tif", "tiff" : Return "image/tiff"
                Case Else : Return "image/jpeg"
            End Select
        End Function

        'Returns the content type based on the supplied image format
        Public Shared Function GetContentType(ByVal RawFormat As ImageFormat) As String
            If RawFormat.Equals(ImageFormat.Jpeg) Then Return "image/jpeg"
            If RawFormat.Equals(ImageFormat.Gif) Then Return "image/gif"
            If RawFormat.Equals(ImageFormat.Bmp) Then Return "image/bmp"
            If RawFormat.Equals(ImageFormat.Png) Then Return "image/png"
            If RawFormat.Equals(ImageFormat.Icon) Then Return "image/icon"
            If RawFormat.Equals(ImageFormat.Tiff) Then Return "image/tiff"
            Return String.Empty
        End Function

        'Returns the content type based on the RawFormat of the supplied image
        Public Shared Function GetContentType(ByVal img As Bitmap) As String
            Return GetContentType(img.RawFormat)
        End Function

        'Returns an ImageFormat object based on the supplied filename extension
        Public Shared Function GetImageFormat(ByVal ext As String) As ImageFormat
            Select Case ext
                Case "jpg", "jpeg", "jpe" : Return ImageFormat.Jpeg
                Case "gif" : Return ImageFormat.Gif
                Case "bmp" : Return ImageFormat.Bmp
                Case "png" : Return ImageFormat.Png
                Case "ico" : Return ImageFormat.Icon
                Case "tif" : Return ImageFormat.Tiff
                Case Else : Return ImageFormat.Jpeg
            End Select
        End Function

        Public Shared Function GetImageFormatFromContentType(ByVal contentType As String) As ImageFormat
            Select Case contentType
                Case "image/jpeg" : Return ImageFormat.Jpeg
                Case "image/gif" : Return ImageFormat.Gif
                Case "image/bmp" : Return ImageFormat.Bmp
                Case "image/png" : Return ImageFormat.Png
                Case "image/ico" : Return ImageFormat.Icon
                Case "image/tif" : Return ImageFormat.Tiff
                Case Else : Return ImageFormat.Jpeg
            End Select
        End Function

#End Region
    End Class
End Namespace

